package com.toubutu.audio.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.toubutu.audio.admin.dto.MergeFileDto;
import com.toubutu.audio.admin.dto.UploadFileDto;
import com.toubutu.audio.common.BusinessConstants;
import com.toubutu.audio.entity.UploadFileRecord;
import com.toubutu.audio.mapper.UploadRecordMapper;
import com.toubutu.audio.mapper.VideoMapper;
import com.toubutu.audio.service.UploadFileRecordService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.toubutu.audio.utils.OssUtils;
import com.toubutu.common.BusinessException;
import com.toubutu.common.StatusCodeEnum;
import com.toubutu.constants.BaseConstants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.stream.Collectors;

/**
 * 文件上传记录
 *
 * @author 戒躁
 * @since 2024-01-08
 */
@Service
@Slf4j
public class UploadFileFileRecordServiceImpl extends ServiceImpl<UploadRecordMapper, UploadFileRecord> implements UploadFileRecordService {
    @Value("${upload.config.upload}")
    private String uploadFilePath;

    @Value("${upload.config.sliceFolder}")
    private String sliceFolder;

    @Resource
    private OssUtils ossUtils;

    @Resource
    private TransactionTemplate transactionTemplate ;
    @Resource
    private VideoMapper videoMapper;

    @Override
    @Transactional(rollbackFor = {Exception.class, IOException.class})
    public String uploadFileBySlice(UploadFileDto uploadFileDto) throws IOException {
        //1. 分片上传,会判断文件是否存在
        String fileUrl = fileIsExist(uploadFileDto.getMd5());
        if (fileUrl != null) {
            //文件已存在直接返回文件地址
            videoMapper.updateVideoSource(uploadFileDto.getVideoCode(),fileUrl);
            return fileUrl;
        }
        String path = sliceFolder + uploadFileDto.getMd5();
        File file = new File(path);
        //判断上传文件夹是否存在
        if (!file.exists()) {
            file.mkdirs();
        }
        //构建文件名称
        MultipartFile uploadMultipartFile = uploadFileDto.getFile();
        //切片文件名称 fileName = md5+_index+后缀
        String originalFilename = uploadFileDto.getFile().getOriginalFilename();
        uploadMultipartFile.transferTo(new File(path + "/" + originalFilename));
        //上传记录入库
        UploadFileRecord record = UploadFileRecord.builder().type(BusinessConstants.FILE_TYPE_SLICE).seq(uploadFileDto.getSeq())
                .fileName(originalFilename)
                .totalSlices(uploadFileDto.getTotalSlices())
                .md5(uploadFileDto.getMd5()).build();
        save(record);
        return null;
    }

    @Override
    public String fileIsExist(String md5) {
        LambdaQueryWrapper<UploadFileRecord> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UploadFileRecord::getMd5, md5).eq(UploadFileRecord::getType, BusinessConstants.FILE_TYPE_WHOLE);
        UploadFileRecord fileRecord = getOne(queryWrapper);
        return fileRecord == null ? null : fileRecord.getUrl();
    }

    @Override
    @Async
    public void mergeFile(MergeFileDto mergeFileDto)  {
        List<UploadFileRecord> fileSliceList = checkSliceAndSort(mergeFileDto);

        String uploadFullFilePath = uploadFilePath + fileSliceList.get(0).getMd5() + "/";
        File uploadFullFileFolder = new File(uploadFullFilePath);
        if (!uploadFullFileFolder.exists()) {
            uploadFullFileFolder.mkdirs();
        }
        //开始合并切片文件
        File fullFile = new File(uploadFullFilePath + mergeFileDto.getMd5()+mergeFileDto.getFileName().substring(mergeFileDto.getFileName().lastIndexOf(".")));
        String sliceFolderPath = sliceFolder + fileSliceList.get(0).getMd5() + "/";
        try (FileChannel fileChannel = new FileOutputStream(fullFile).getChannel()) {
            for (UploadFileRecord fileRecord : fileSliceList) {
                try (FileChannel sliceChannel = new FileInputStream(sliceFolderPath + fileRecord.getFileName()).getChannel()) {
                    sliceChannel.transferTo(0, sliceChannel.size(), fileChannel);
                }
            }
            //删除上传数据库记录
            LambdaQueryWrapper<UploadFileRecord> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(UploadFileRecord::getMd5, mergeFileDto.getMd5())
                    .eq(UploadFileRecord::getType, BusinessConstants.FILE_TYPE_SLICE);
            remove(queryWrapper);
            //删除切片文件
            deleteFileSlices(sliceFolderPath);
        } catch (IOException e) {
            log.info(e.getMessage());
            //删除切片文件
            //删除上传数据库记录
            LambdaQueryWrapper<UploadFileRecord> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(UploadFileRecord::getMd5, mergeFileDto.getMd5())
                    .eq(UploadFileRecord::getType, BusinessConstants.FILE_TYPE_SLICE);
            remove(queryWrapper);
            deleteFileSlices(sliceFolderPath);
            return;
        }
        String fileUrl;
        try {
            //上传至阿里云oss
            fileUrl = ossUtils.fileSliceUpload(fullFile);
            fullFile.delete();
        }catch (Exception e){
            log.error("文件上传阿里云失败: {}",e.getMessage());
            return;
        }
        if (fileUrl!=null){
            transactionTemplate.execute(new TransactionCallbackWithoutResult() {
                @Override
                protected void doInTransactionWithoutResult(TransactionStatus status) {
                    try {
                        //新增一条完整记录
                        UploadFileRecord uploadFileRecord = UploadFileRecord.builder().md5(mergeFileDto.getMd5())
                                .url(fileUrl)
                                .type(BusinessConstants.FILE_TYPE_WHOLE).fileName(fullFile.getName())
                                .seq(null).build();
                        save(uploadFileRecord);
                        //设置视频地址
                        videoMapper.updateVideoSource(mergeFileDto.getVideoCode(),fileUrl);
                    }catch (Exception e){
                        status.setRollbackOnly();
                    }
                }
            });
        }


    }

    /**
     * 检查切片文件并且按照索引排序
     *
     * @param mergeFileDto mergeFileDto
     * @return List<UploadFileRecord>
     */
    private List<UploadFileRecord> checkSliceAndSort(MergeFileDto mergeFileDto) {
        LambdaQueryWrapper<UploadFileRecord> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UploadFileRecord::getMd5, mergeFileDto.getMd5())
                .eq(UploadFileRecord::getIsDelete, BaseConstants.UN_DELETED)
                .eq(UploadFileRecord::getType, BusinessConstants.FILE_TYPE_SLICE)
                .orderByAsc(UploadFileRecord::getSeq);
        List<UploadFileRecord> fileSliceList = list(queryWrapper);
        if (fileSliceList.isEmpty()) {
            throw new BusinessException(StatusCodeEnum.NOT_FOUND.getCode(), "文件切片不存在!");
        }
        if (!fileSliceList.get(0).getTotalSlices().equals(fileSliceList.size())) {
            throw new BusinessException(StatusCodeEnum.USER_HANDLE.getCode(), "文件损坏，请重新上传!");
        }
        return fileSliceList;
    }

    private void deleteFileSlices(String sliceFolder) {
        File folder = new File(sliceFolder);
        if (folder.exists() && folder.isDirectory()) {
            File[] files = folder.listFiles();
            for (File file : files) {
                if (file.isDirectory()) {
                    deleteFileSlices(file.getAbsolutePath()); // 递归删除子文件夹
                } else {
                    file.delete(); // 删除文件
                }
            }
            folder.delete(); // 删除文件夹
        }

    }
}
